package edu.ufl.cise.sanitas.proxy;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Scanner;

import edu.ufl.cise.sanitas.database.SanitasDBManager;
import edu.ufl.cise.sanitas.types.comm.ConnectionStatus;
import edu.ufl.cise.sanitas.types.comm.CreateNewAccountRequest;
import edu.ufl.cise.sanitas.types.comm.FetchAllNewsfeedRequest;
import edu.ufl.cise.sanitas.types.comm.NewPostRequest;
import edu.ufl.cise.sanitas.types.comm.PortComfirmationPacket;
import edu.ufl.cise.sanitas.types.comm.QueryAllConditionsRequest;
import edu.ufl.cise.sanitas.types.comm.Request;
import edu.ufl.cise.sanitas.types.comm.Response;
import edu.ufl.cise.sanitas.types.comm.SigninRequest;
import edu.ufl.cise.sanitas.types.comm.SyncPostsRequest;
import edu.ufl.cise.sanitas.types.comm.UpdateUserConditionsRequest;
import edu.ufl.cise.sanitas.types.data.ActiveUser;
import edu.ufl.cise.sanitas.types.data.Condition;
import edu.ufl.cise.sanitas.types.data.Feed;
import edu.ufl.cise.sanitas.types.data.User;
import edu.ufl.cise.sanitas.types.comm.Types;

/**
 * Class for process client's requests
 * 
 */

/**
 * @author tan
 * 
 */
public class RequestProcessor implements Runnable {

	private SanitasDBManager sdbm = new SanitasDBManager();

	private byte[] responseData = new byte[10000];  // response in bytes format
	private Request request;

	DatagramPacket packet;

	// connection state
	protected ConnectionState weakConState; // weak connection state
	protected ConnectionState reliConState; // reliable connection state
	protected ConnectionState curState;

	protected DatagramSocket socket;
	protected BufferedReader in = null;

	private InetAddress address;
	private int port;

	private int userId;
	private String userName;
	private String password;
	private String theme;
	private String content;

	private String timeStamp;
	private Feed bcastFeed;
	private ArrayList<ActiveUser> activeUserGroup;

	private int groupPort;

	private DatagramPacket bPacket;

	public RequestProcessor(DatagramSocket socket, DatagramPacket packet,
			ArrayList<ActiveUser> activeUserGroup) throws IOException {

		this.activeUserGroup = activeUserGroup;
		
		reliConState = new ReliableConnectionState(this, sdbm);
		weakConState = new WeakConnectionState(this, sdbm);

		curState = reliConState; // default state

		this.packet = packet;
		this.socket = socket;
	}

	public void run() {

		try {

			byte[] requestBuf = packet.getData();

			try {
				// read input packet, conver byte to a object
				ByteArrayInputStream bis = new ByteArrayInputStream(requestBuf);

				Object obj = new ObjectInputStream(bis).readObject();

				bis.close();
				
				
				if (obj instanceof PortComfirmationPacket) {
					
					groupPort = packet.getPort();
					
					InetAddress userAddr = packet.getAddress();
					
					System.out.println("Port for active user is " + groupPort);
					
					// test if user in the group
					boolean hasUser = false;

					for (ActiveUser testUser : activeUserGroup) {

						if (testUser.getPort() == groupPort) {
							hasUser = true;
						} else {
							hasUser = false;
						}
					}

					if (hasUser == false) {
						
						ActiveUser activeUser = new ActiveUser(userAddr, groupPort);
						
						activeUserGroup.add(activeUser);

						System.out.println("single addr" + userAddr);
						
						System.out.println("single port" + groupPort);
						
						System.out.println("Add user into group");
					}

					System.out.println("Group Size " + activeUserGroup.size());
					
				} else {
					request = (Request) obj;

					// change state based on request
					if (request.getConnectionStatus() == ConnectionStatus.STRONG) {
						curState = reliConState;
						System.out.println("Proxy switch to Strong connection state!");
						
					} else {
						curState = weakConState;
						System.out.println("Proxy switch to weak connection state!");
						
					}

					switch (request.getType()) {

					case CREATE_NEW_ACCOUNT:
						
						System.out.println("CREATE_NEW_ACCOUNT REQUEST");

						CreateNewAccountRequest cnar = (CreateNewAccountRequest) request;

						User newUser = cnar.getUser();

						responseData = createNewAccount(newUser);
						break;

					case SIGN_IN:

						SigninRequest signIn = (SigninRequest) request;

						String userName = signIn.getUsername();
						String pwd = signIn.getPassword();

						port = packet.getPort();

						responseData = userLogin(userName, pwd);
						break;

					case NEW_POST:

						NewPostRequest newFeed = (NewPostRequest) request;

						userId = newFeed.getUserId();
						userName = newFeed.getUserName();
						timeStamp = newFeed.getTimeStamp();

						
						System.out.println(timeStamp);
						
						// theme:: general, obesity...etc...
						Condition cond = newFeed.getCondition();

						String theme = cond.getConditionName();

						content = newFeed.getContent();

						System.out.println("Theme from Request Process: " + theme);
						System.out.println("Content from Request Process: " + content);

						bcastFeed = new Feed(timeStamp, userName, theme, content);

						System.out.println("Boardcast");

						byte[] bData = objectData(bcastFeed);

						System.out.println("Group Size: " + activeUserGroup.size());

						for (ActiveUser singleUser : activeUserGroup) {

							InetAddress bAddr = singleUser.getAddress();
							int bPort = singleUser.getPort();

							bPacket = new DatagramPacket(bData, bData.length, bAddr, bPort);

							System.out.println("Bcast send to " + bAddr + ": " + bPort);

							socket.send(bPacket);
						}

						responseData = bcaseNewFeed(new Feed(timeStamp, userName, theme, content));

						break;

					case FETCH_ALL_NEWSFEED:

						
						System.out.println("FETCH_ALL_NEWSFEED REQUEST");
						
						FetchAllNewsfeedRequest fetchFeed = (FetchAllNewsfeedRequest) request;

						userId = fetchFeed.getUserId();

						responseData = fetchAllNewsFeed(userId);

						break;

					case SYNC_POSTS:
						
						System.out.println("SYNC_POSTS REQUEST");
						
						SyncPostsRequest syncPost = (SyncPostsRequest) request;
						
						// same as bcast
						Feed[] feedList = syncPost.getFeedList();
						
						
						for (Feed feed : feedList) {

							bcaseNewFeed(feed);
							
							userName = feed.getUserName();
							timeStamp = feed.getTimeStamp();
							
							// theme:: general, obesity...etc...
							theme = feed.getTheme();
							content = feed.getContent();

							System.out.println("Sync Post Theme from Request Process: " + theme);
							System.out.println("Sync Post Content from Request Process: " + content);

							bcastFeed = new Feed(timeStamp, userName, theme, content);

							System.out.println("Sync Post Boardcast");

							byte[] syncbData = objectData(bcastFeed);

							for (ActiveUser singleUser : activeUserGroup) {

								InetAddress bAddr = singleUser.getAddress();
								int bPort = singleUser.getPort();

								bPacket = new DatagramPacket(syncbData, syncbData.length, bAddr, bPort);

								System.out.println("Bcast send to " + bAddr + ": " + bPort);

								socket.send(bPacket);
							}
						}
						
						responseData = syncPost();
						
						break;
						
					case UPDATE_USER_CONDITIONS:
						System.out.println("UPDATE_USER_CONDITIONS");
						
						UpdateUserConditionsRequest upUserCond = (UpdateUserConditionsRequest) request;
						
						userName = upUserCond.getUsername();
						password = upUserCond.getPassword();
						String[] conditions = upUserCond.getConditionList();
						
						responseData = updateUserConditions(userName, password, conditions);
						
						
						
					case QUERY_ALL_CONDITIONS:
						
						System.out.println("QUERY_ALL_CONDITIONS");
						
//						QueryAllConditionsRequest qAllCond = (QueryAllConditionsRequest) request;
						
						responseData = queryAllConditions();
						
						break;
					case SIGN_OFF:

						break;

					default:
						break;
					}

					// figure out response
					address = packet.getAddress();
					port = packet.getPort();

					packet = new DatagramPacket(responseData, responseData.length, address, port);

					socket.send(packet);

					System.out.println("Response send to " + address + " : " + port
							+ " Length is " + packet.getLength());

				}
				System.out.println();
				System.out.println("Destory the thread...");
				System.out.println();

			} catch (IOException e) {
				e.printStackTrace();
			}

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void swithConState(char kb) {

		switch (kb) {
		case 'w':
			curState = weakConState;
			System.out.println(curState);
			break;
		case 'r':
			curState = reliConState;
			System.out.println(curState);
			break;
		default:
			System.out.println("Please enter w, r or d");
			break;
		}
	}

	public byte[] createNewAccount(User newUser) {
		return curState.createNewAccount(newUser);
	}

	public byte[] userLogin(String userName, String pwd) {
		return curState.userLogin(userName, pwd);
	}

	public byte[] fetchAllNewsFeed(int userId) {
		return curState.fetchAllNewsFeed(userId);
	}

	public byte[] bcaseNewFeed(Feed feed) {
		return curState.bcastNewFeed(feed);
	}

	public byte[] syncPost() {
		return curState.syncPost();
	}

	public byte[] updateUserConditions(String userName, String pwd, String[] conditions) {
		return curState.updateUserConditions(userName, pwd, conditions);
	}
	
	public byte[] queryAllConditions() {
		return curState.queryAllConditions();
	}
	
	public void pushQuery() {
		curState.pushQuery();
	}

	public void setState(ConnectionState state) {
		this.curState = state;
	}

	public void setState(String state) {

		if (state == "r")
			this.curState = reliConState;
		if (state == "w")
			this.curState = weakConState;
	}

	public ConnectionState getConnectionState() {
		return curState;
	}

	public ConnectionState getWeakConState() {
		return weakConState;
	}

	public ConnectionState getReliConState() {
		return reliConState;
	}

	/**
	 * Covert a object to bytes
	 * 
	 * @param response
	 * @return
	 */
	public byte[] objectData(Object obj) {

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos;
		try {
			oos = new ObjectOutputStream(bos);

			oos.writeObject(obj);
			oos.flush();
			oos.close();
			bos.close();

		} catch (IOException e) {
			e.printStackTrace();
		}

		responseData = bos.toByteArray();

		return responseData;
	}
}
